﻿using System;
using System.Collections.Generic;
using System.Text;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Media;
using Microsoft.Xna.Framework.Net;
using Microsoft.Xna.Framework.Storage;
using System.Xml.Serialization;

namespace zEngine
{
    public class zModel
    {
        #region Static functions
        public static Matrix[] BonesTransformation(Model model)
        {
            Matrix[] bones = new Matrix[model.Bones.Count];
            model.CopyAbsoluteBoneTransformsTo(bones);
            return bones;
        }
        public static void DrawModelWithBasicEffect(Model model, Matrix[] bones, Matrix world, Matrix view, Matrix proj)
        {
            foreach (ModelMesh mesh in model.Meshes)
            {
                foreach (BasicEffect eff in mesh.Effects)
                {
                    eff.EnableDefaultLighting();
                    eff.World = bones[mesh.ParentBone.Index] * world;
                    eff.View = view;
                    eff.Projection = proj;

                }
                mesh.Draw();
            }
        }

        public static int GetVerticesCount(Model model)
        {
            int count = 0;
            foreach (ModelMesh mesh in model.Meshes)
            {
                foreach (ModelMeshPart part in mesh.MeshParts)
                {
                    count += part.NumVertices;
                }
            }
            return count;
        }

        public static int GetPrimativesCount(Model model)
        {
            int count = 0;
            foreach (ModelMesh mesh in model.Meshes)
            {
                foreach (ModelMeshPart part in mesh.MeshParts)
                {
                    count += part.PrimitiveCount;
                }
            }
            return count;
        }

        public static Vector3[] GetVerticesPosition(Model Model3d, Matrix[] BoneTransforms)
        {
            List<Vector3> result = new List<Vector3>();
            // Scan through to get the list of vertices
            foreach (ModelMesh mesh in Model3d.Meshes)
            {
                Matrix meshTransform = BoneTransforms[mesh.ParentBone.Index];

                foreach (ModelMeshPart part in mesh.MeshParts)
                {
                    int stride = part.VertexStride;
                    int numberv = part.NumVertices;
                    VertexDeclaration test1 = part.VertexDeclaration;      // not used for now
                    byte[] data = new byte[stride * numberv];

                    mesh.VertexBuffer.GetData<byte>(data);

                    for (int ndx = 0; ndx < data.Length; ndx += stride)
                    {
                        float floatvaluex = BitConverter.ToSingle(data, ndx);
                        float floatvaluey = BitConverter.ToSingle(data, ndx + 4);
                        float floatvaluez = BitConverter.ToSingle(data, ndx + 8);
                        Vector3 vectCurrentVertex = new Vector3(floatvaluex, floatvaluey, floatvaluez);
                        Vector3 vectWorldVertex = Vector3.Transform(vectCurrentVertex, meshTransform);

                        result.Add(vectWorldVertex);
                    }
                }
            }

            return result.ToArray();
        }

        /// <summary>
        /// Create a bounding box for the input model
        /// </summary>
        public static BoundingBox CreateBoundingBox(Model Model3d, Matrix[] BoneTransforms)
        {
            if (Model3d == null)
            {
                return new BoundingBox();
            }

            Vector3 Max = new Vector3(float.MinValue, float.MinValue, float.MinValue);
            Vector3 Min = new Vector3(float.MaxValue, float.MaxValue, float.MaxValue);

            foreach (ModelMesh mesh in Model3d.Meshes)
            {
                Matrix meshTransform = BoneTransforms[mesh.ParentBone.Index];

                foreach (ModelMeshPart part in mesh.MeshParts)
                {
                    int stride = part.VertexStride;
                    int numberv = part.NumVertices;
                    VertexDeclaration test1 = part.VertexDeclaration;      // not used for now
                    byte[] data = new byte[stride * numberv];

                    mesh.VertexBuffer.GetData<byte>(data);

                    for (int ndx = 0; ndx < data.Length; ndx += stride)
                    {
                        float floatvaluex = BitConverter.ToSingle(data, ndx);
                        float floatvaluey = BitConverter.ToSingle(data, ndx + 4);
                        float floatvaluez = BitConverter.ToSingle(data, ndx + 8);
                        Vector3 vectCurrentVertex = new Vector3(floatvaluex, floatvaluey, floatvaluez);
                        Vector3 vectWorldVertex = Vector3.Transform(vectCurrentVertex, meshTransform);

                        if (vectWorldVertex.X < Min.X) Min.X = vectWorldVertex.X;
                        if (vectWorldVertex.X > Max.X) Max.X = vectWorldVertex.X;
                        if (vectWorldVertex.Y < Min.Y) Min.Y = vectWorldVertex.Y;
                        if (vectWorldVertex.Y > Max.Y) Max.Y = vectWorldVertex.Y;
                        if (vectWorldVertex.Z < Min.Z) Min.Z = vectWorldVertex.Z;
                        if (vectWorldVertex.Z > Max.Z) Max.Z = vectWorldVertex.Z;
                    }
                }
            }
            return new BoundingBox(Min, Max);
        }

        /// <summary>
        /// Create bounding sphere for input model
        /// </summary>
        public static BoundingSphere CreateBoundingSphere(Model Model3d, Matrix[] BoneTransforms)
        {
            BoundingSphere? finalSphere = null;
            foreach (ModelMesh mesh in Model3d.Meshes)
            {
                BoundingSphere bsphere = mesh.BoundingSphere;
                // Transform the bounding sphere
                BoundingSphere transSphere = TransformBoundingSphere(bsphere, BoneTransforms[mesh.ParentBone.Index]);

                // Combine the bounding sphere
                if (!finalSphere.HasValue)
                {
                    finalSphere = transSphere;
                }
                else
                {
                    finalSphere = BoundingSphere.CreateMerged(finalSphere.Value, transSphere);
                }
            }
            return finalSphere.Value;
        }

        /// <summary>
        /// Perform a transformation over a bounding sphere
        /// </summary>
        public static BoundingSphere TransformBoundingSphere(BoundingSphere bSphere, Matrix transformMatrix)
        {
            BoundingSphere result;

            Vector3 tra;
            Vector3 sca;
            Quaternion rot;
            transformMatrix.Decompose(out sca, out rot, out tra);

            float maxScale = sca.X;
            if (maxScale < sca.Y)
                maxScale = sca.Y;
            if (maxScale < sca.Z)
                maxScale = sca.Z;

            float bsRadius = bSphere.Radius * maxScale;
            Vector3 bsCenter = Vector3.Transform(bSphere.Center, transformMatrix);

            result = new BoundingSphere(bsCenter, bsRadius);

            return result;
        }

        /// <summary>
        /// Perform a transformation over a bounding box
        /// </summary>
        public static BoundingBox TransformBoundingBox(BoundingBox box, Matrix transformMatrix)
        {
            BoundingBox result;

            Vector3[] corners = box.GetCorners();

            for (int i = 0; i < corners.Length; i++)
            {
                corners[i] = Vector3.Transform(corners[i], transformMatrix);
            }
            result = BoundingBox.CreateFromPoints(corners);
            return result;
        }

        /// <summary>
        /// Resize the scale to fit in the prefix size
        /// </summary>
        public static void ResizeToPrefixSize(ref Model model, float PresetSize)
        {
            if (PresetSize > 0)
            {
                BoundingSphere sphere = CreateBoundingSphere(model, BonesTransformation(model));

                float fScale = PresetSize / (sphere.Radius * 2);
                // Resize the root
                model.Root.Transform = model.Root.Transform * Matrix.CreateScale(fScale);
            }
        }
        #endregion
    }
}
